/**
 * @fileoverview
 * This script defines a widget for fetching and displaying images from www.fly-ted.org.
 * @author <a href="http://purl.org/net/aliman">Alistair Miles</a>, Graham Klyne, Jun Zhao
 * @version $Revision:538 $ on $Date: 2008-08-27 09:08:41 +0100 (Wed, 27 Aug 2008) $ by $Author: aliman $
 * @requires YAHOO.util.Connect
 * @requires flyui
 * @requires flyui.bdgp.Service
 * @requires flyui.flybase.Gene
 * TODO license terms
 */
 
// create a namespace if not already defined
flyui.namespace("flyui.bdgp");


/*
 * --------------------------------------------------
 * WIDGET
 * --------------------------------------------------
 */


/**
 * Create a widget.
 * @class
 * A widget for displaying in situ images from BDGP.
 * @constructor
 * @param {flyui.bdgp.Service} service the service to use to fetch data
 * @param {flyui.bdgp.DefaultRenderer} renderer the renderer to use
 */
flyui.bdgp.ImageWidget = function( service, renderer ) {
	try {
    	// do initialisation
    	this._init(service, renderer);
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget", error);
    }

};


/**
 * @private
 * @type flyui.bdgp.Controller
 */
flyui.bdgp.ImageWidget.prototype._controller = null;

	
/**
 * @private
 * Initialise the widget.
 * @param {flyui.bdgp.Service} service the service to use to fetch data
 * @param {flyui.bdgp.DefaultRenderer} renderer the renderer to use
 */
flyui.bdgp.ImageWidget.prototype._init = function( service, renderer ) {
	try {
		// create a model
		var model = new flyui.mvcutils.GenericModel2();
		model.setDefinition(flyui.bdgp.ImageWidget.modelDefinition);
		
		// instantiate the controller
		this._controller = new flyui.bdgp.ImageWidget.Controller(model, service, this);
		
		// connect the renderer to the model
		renderer.connect(model);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.prototype._init", error);
    }
	
};


/**
 * Find images by any label of a gene product.
 * @param {String} geneLabel any gene product label
 */
flyui.bdgp.ImageWidget.prototype.findImagesByAnyGeneLabel = function( geneLabel ) {
	try {
		// pass through to controller
		this._controller.findImagesByAnyGeneLabel(geneLabel);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.prototype.findImagesByAnyGeneLabel", error);
    }
	
};


/**
 * Find images by the given gene.
 * @param {flyui.flybase.Gene} gene
 */
flyui.bdgp.ImageWidget.prototype.findImagesByGene = function( gene ) {
	try {
		// pass through to controller
		this._controller.findImagesByGene(gene);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.prototype.findImagesByGene", error);
    }
	
}


/*
 * --------------------------------------------------
 * CONTROLLER
 * --------------------------------------------------
 */


/**
 * Create a controller.
 * @class
 * A controller class for the BDGP image widget internal MVC.
 * @constructor
 * @param {flyui.mvcutils.GenericModel2} model the model to store widget state data
 * @param {flyui.bdgp.Service} service the service to use to fetch data
 * @param {flyui.bdgp.ImageWidget} widget the widget to control
 */

flyui.bdgp.ImageWidget.Controller = function( model, service, widget ) {
	
	var that = this;
	
	/**
	 * @private
	 */
	this._model = model;
	
	/**
	 * @private
	 */
	this._service = service;
	
	/**
	 * @private
	 */
	this._parent = widget;

    /**
     * @private
     * Success case callback for find images operations.
     * @param {Array<flyui.bdgp.Image>} images 
     */
	this._findImagesSuccess = function (images){
		
		flyui.info("request success");
		
		// set the results
		that._model.set("RESULTS", images);
		
		// set the state
		that._model.set("STATE", "READY");
		
	};
	
	/**
	 * @private
	 * Failure case callback for the find images operations.
	 * @param response HTTP response object (YUI)
	 */
	this._findImagesFailure = function (response) {
		
		flyui.err("request failed, status "+response.status+" "+response.statusText);
		
		// set an error message
		var msg = "There was an error retrieving data from BDGP, see the logs for more info. The server may be busy or down, please try again later. If this message persists, please contact the Image Bioinformatics Research Group at bioimage@mail.ontonet.org.";		
		that._model.set("ERRORMESSAGE", msg);

		// set the state
		that._model.set("STATE", "SERVERERROR");
			
	}
	
};


/**
 * Find images by any gene product label
 * @param {String} query any gene product label
 */
flyui.bdgp.ImageWidget.Controller.prototype.findImagesByAnyGeneLabel = function (query){
	try {
		// pass through to private implementation
		this._findImagesByAnyGeneLabel(query, this._findImagesSuccess, this._findImagesFailure);
	
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.Controller.prototype.findImagesByAnyGeneLabel", error);
    }
};


/**
 * @private
 * Private implementation of find images operation, with callback injection to enable testing.
 * @param {String} geneLabel any gene product label
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flyui.bdgp.ImageWidget.Controller.prototype._findImagesByAnyGeneLabel = function(geneLabel, success, failure) {
	try {
		flyui.info("flyui.bdgp.ImageWidget.Controller._findImagesByAnyGeneLabel :: request: "+geneLabel);
		
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", geneLabel);
		
		// kick off the request
		this._service.findImagesByAnyGeneLabel(geneLabel, success, failure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.Controller.prototype._findImagesByAnyGeneLabel", error);
    }
};


/**
 * Find images by a given gene.
 * @param {flyui.flybase.Gene} gene 
 */
flyui.bdgp.ImageWidget.Controller.prototype.findImagesByGene = function( gene ) {
	try {
		// pass through to private implementation
		this._findImagesByGene(gene, this._findImagesSuccess, this._findImagesFailure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.Controller.prototype.findImagesByGene", error);
    }
	
};


/**
 * @private
 * Private implementation of find images operation, with callback injection to enable testing.
 * @param {flyui.flybase.Gene} gene 
 * @param {Function} success success case callback
 * @param {Function} failure failure case callback
 */
flyui.bdgp.ImageWidget.Controller.prototype._findImagesByGene = function( gene, success, failure ) {
	try {
		flyui.info("flyui.flyted.ImageWidget.Controller._findImagesByGene :: request: "+gene.flybaseID);
		
		// set the model pending
		this._model.set("STATE", "PENDING");
		
		// set the query property
		this._model.set("QUERY", gene);
		
		// kick off the request
		this._service.findImagesByAnyGeneLabel(gene.flybaseID, success, failure);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.Controller.prototype._findImagesByGene", error);
    }
	
};


/*
 * --------------------------------------------------
 * MODEL DEFINITION
 * --------------------------------------------------
 */


/**
 * Widget MVC model definition.
 */
flyui.bdgp.ImageWidget.modelDefinition = {
	properties : [ "STATE", "RESULTS", "QUERY", "ERRORMESSAGE" ],
	
	values : {
		"STATE" : [ "PENDING", "READY", "SERVERERROR", "UNEXPECTEDERROR" ]
	},
	
	initialize : function( data ) {
		data["STATE"] = "READY";
		data["RESULTS"] = null;
		data["QUERY"] = null;
		data["ERRORMESSAGE"] = null;
	}
}


/*
 * --------------------------------------------------
 * DEFAULT RENDERER
 * --------------------------------------------------
 */


/**
 * Create a default renderer for the widget.
 * @class
 * A default renderer for the BDGP image widget.
 */
flyui.bdgp.ImageWidget.DefaultRenderer = function() {};


/**
 * @private
 * @type Element
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._canvas = null;

/**
 * @private
 * @type Element
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._pendingPane = null;

/**
 * @private
 * @type Element
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._resultsPane = null;

/**
 * @private
 * @type Element
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._resultsSummaryPane = null;

/**
 * @private
 * @type Element
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._messagePane = null;


/**
 * Set the element (canvas) to which this renderer will apply and initialise it.
 * @param {Element} canvas
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype.setCanvas = function( canvas ) {
	
	this._canvas = canvas;
	this._initCanvas();
	
}


/**
 * @private
 * Initialise the canvas.
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._initCanvas = function() {
 	try {
	    YAHOO.util.Dom.addClass(this._canvas, "bdgpImageWidget");
	    
	    // set up the pending pane
	    this._pendingPane = document.createElement("p");
	    this._pendingPane.innerHTML = "pending...";
	    this._canvas.appendChild(this._pendingPane);
	    YAHOO.util.Dom.addClass(this._pendingPane, "pendingPane");
	    flyui.mvcutils.hide(this._pendingPane);
	    
	    // set up the message pane
	    this._messagePane = document.createElement("p");
	    this._messagePane.innerHTML = "this should never be displayed";
	    this._canvas.appendChild(this._messagePane);
	    YAHOO.util.Dom.addClass(this._messagePane, "messagePane");
	    flyui.mvcutils.hide(this._messagePane);
	    
	    // setup results summary pane
	    this._resultsSummaryPane = document.createElement("p");
	    this._canvas.appendChild(this._resultsSummaryPane);
	    YAHOO.util.Dom.addClass(this._resultsSummaryPane, "resultsSummaryPane");
	    flyui.mvcutils.hide(this._resultsSummaryPane);
	        
	    // setup results pane
	    this._resultsPane = document.createElement("div");
	    this._canvas.appendChild(this._resultsPane);
	    YAHOO.util.Dom.addClass(this._resultsPane, "resultsPane");
	    flyui.mvcutils.hide(this._resultsPane);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._initCanvas", error);
    }    
}


/**
 * @private
 * Main callback function for model changes.
 * @param {String} type the model property name
 * @param {Array} args the from and to values
 * @param {flyui.bdgp.ImageWidget.DefaultRenderer} self a self reference
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onModelChanged = function( type, args, self ) {
	try {
		// map property names to functions
	    var handlers = {
	        "STATE":"_onStateChanged",
	        "QUERY":"_onQueryChanged",
	        "RESULTS":"_onResultsChanged",
	        "ERRORMESSAGE":"_onErrorMessageChanged"
	    };
	    
	    // select the correct handler function
	    var handler = handlers[type];
	    
	    // call the handler
	    self[handler](args[0], args[1]);
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onModelChanged", error);
    }
    	
}


/**
 * @private
 * Handle a model state change.
 * @param {String} from the old state
 * @param {String} to the new state
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onStateChanged = function (from, to) {
	try {
	    if ( to == "PENDING" ) {
	        flyui.mvcutils.show(this._pendingPane);
	        flyui.mvcutils.hide(this._messagePane);
	        flyui.mvcutils.hide(this._resultsSummaryPane);
	        flyui.mvcutils.hide(this._resultsPane);
	    } 
	    else if (to == "READY") {
	        flyui.mvcutils.hide(this._pendingPane);
	        flyui.mvcutils.hide(this._messagePane);
	        flyui.mvcutils.show(this._resultsSummaryPane);
	        flyui.mvcutils.show(this._resultsPane);     
	    }
	    else if ( to == "SERVERERROR" || to == "UNEXPECTEDERROR" ) {
	        flyui.mvcutils.hide(this._pendingPane);
	        flyui.mvcutils.show(this._messagePane);
	        flyui.mvcutils.hide(this._resultsSummaryPane);
	        flyui.mvcutils.hide(this._resultsPane);         
	    } 
	    else {
	        // this should never happen
	        throw {name:"flyui.bdgp.ImageWidget.UnexpectedStateError", message:"Invalid state: "+newState};
	    }
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onStateChanged", error);
    }
};


/**
 * @private
 * Handle an error message change.
 * @param {String} from the old message
 * @param {String} to the new message
 */    
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onErrorMessageChanged = function (from, to){
	try {
    	this._messagePane.innerHTML = to;
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onErrorMessageChanged", error);
    }
    
};
    
    
/**
 * @private
 * Handle model results change.
 * @param {Array<flyui.bdgp.Image>} from the old results
 * @param {Array<flyui.bdgp.Image>} to the new results
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onResultsChanged = function(from, to) {
	try {
	    // render the results summary
	    var numberOfResults = 0;
	    
	    if (to.length > 0){
	    	for (var i=0; i < to.length; i++){
	    		numberOfResults += to[i].images.length;
	    	}
	    }
	    
	    this._renderResultsSummary(this._query, numberOfResults);
	    flyui.info("the number of results " + to.length);
	    
	    if (to.length > 0) { // if there are some results to render
	        
	        // empty the content
	        this._resultsPane.innerHTML = "";
	        
	        // set the content
	        var resultsNode = this._imagesToDivHTML(to);
	        flyui.debug("results node content:"+resultsNode.innerHTML);
	        this._resultsPane.appendChild(resultsNode);
	        
	    }
	    else {
	    	
	    	// clear out the content
	        this._resultsPane.innerHTML = "";
	        
	    }
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onResultsChanged", error);
    }
};


/**
 * Handle model query changed.
 * @param {String} from the old query
 * @param {String} to the new query
 */    
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onQueryChanged = function (from, to){
	try {
    	// don't do anything now, store the query and wait till we have more
    	this._query = to;
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._onQueryChanged", error);
    }

}
   
   
/**
 * Connect the renderer to a model.
 * @param {flyiu.mvcutils.GenericModel2} model
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype.connect = function( model ) {
	try {
    	model.subscribeAll(this._onModelChanged, this);
    
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype.connect", error);
    }
}    


/**
 * @private
 * Render the results summary pane.
 * @param {String or flyui.flybase.Gene} query the user's query
 * @param {Number} count the number of results
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._renderResultsSummary = function( query, count ) {
	try {
		// build the inner HTML content
	    var content = "found ";
	    content += count;
	    content += " matching image";    
	    content += (count == 0 || count > 1) ? "s " : " ";
	    content += "from <a href='http://www.fruitfly.org'>www.fruitfly.org</a> for ";
	
	    if (query instanceof flyui.flybase.Gene) {
	        content += "gene <strong>"+query.symbols.join(" / ")+"</strong>";
	        if (count > 0) {
	            content += " (BDGP report: <a href='http://www.fruitfly.org/cgi-bin/ex/bquery.pl?qtype=report&find="+query.annotationSymbols[0]+"&searchfield=CG'>"+query.annotationSymbols[0]+"</a>)";
	        }
	    } else {
	        content +=  "query '"+query+"'";
	    }
	    if (count > 0) {
    	    content += "...";
	    }
	    // render the content
	    this._resultsSummaryPane.innerHTML = content;
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._renderResultsSummary", error);
    }
    
}

flyui.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList = function(outerTag, innerTag, list) {
	try {
		var outerElem = document.createElement(outerTag);
	
		// can we defer this to caller?  tbody.appendChild(headerRow);
		
		flyui.debug("creating outer "+outerTag);
		for (var i=0; i<list.length; i++) {
			var innerElem = document.createElement(innerTag);
			innerElem.innerHTML = list[i];
			outerElem.appendChild(innerElem);
		}	
	
		return outerElem;
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList", error);
    }	
}

/**
 * @private
 * Render an array of image objects as HTML content.
 * Override this method to get custom rendering.
 * @param {Array<flyui.bdgp.Image>} images the images to render
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._imagesToDivHTML = function( stages ) {
    try {
	    // build the content
	    
	    flyui.debug("create the table");
		
		var table = document.createElement("table");
		var thead = document.createElement("thead");
		var tbody = document.createElement("tbody");
		table.appendChild(thead);
		table.appendChild(tbody);
		
		// set up header row
		var headers = ["stage and expressions", "images"];
		var headerRow = flyui.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList("tr", "th", headers); 
		thead.appendChild(headerRow);
		
		flyui.debug("rendering the header " + headers.innnerHTML);
	   
	    for ( var s=0; s<stages.length; s++){
	    	flyui.debug("creating result row "+stages[s].stageName);
	   	
	    	var cellContents = this._imageToTableCellContents(stages[s]);
			var row = flyui.bdgp.ImageWidget.DefaultRenderer.renderDomElementFromList("tr", "td", cellContents);
			tbody.appendChild(row);    	
	    	
	    }
	    return table;
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._imagesToDivHTML", error);
    }	
    
};

flyui.bdgp.ImageWidget.DefaultRenderer.prototype._imageToTableCellContents = function( stage ) {
	try {
		var imageSet = stage.images;
		
		var firstColumn = stage.stageName + this._expressionTermsToDivHTML(imageSet[0]);
			
		var secondColumn = "";
		
		for ( var i=0; i<imageSet.length; i++ ) {
	    
	    	secondColumn += this._imageToDivHTML(imageSet[i]);
		}
		
		return [firstColumn, secondColumn];
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._imageToTableCellContents", error);
    }
}

flyui.bdgp.ImageWidget.DefaultRenderer.prototype._expressionTermsToDivHTML = function ( image ){
	
	try {
		var content = "<div class=\"terms\">";
		var expressionTerms = image.expressions;
		content += "<ul>";
		for (var t=0; t<expressionTerms.length; t++){
			content += "<li>" + expressionTerms[t] + "</li>";
		}
		content += "</ul></div>";
		return content;
	}catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._expressionTermsToDivHTML", error);
    }
}

/**
 * @private
 * Render an image object as HTML content.
 * Override this method to get custom rendering.
 * @param {flyui.bdgp.Image} image the image to render
 */
flyui.bdgp.ImageWidget.DefaultRenderer.prototype._imageToDivHTML = function( image ) {
    
    try {
	    var content =   "<div class=\"result\">";
	    content +=          "<a href=\"" + image.fullImageURL + "\">";
	    content +=              "<img src=\"" + image.thumbnailURL + "\" alt=\"this is an image\">";
	    content +=          "</img></a>"; 
	    content +=      "</div>";
	    
	    return content;
    }catch (error) {
        	throw new flyui.UnexpectedException("flyui.bdgp.ImageWidget.DefaultRenderer.prototype._imageToDivHTML", error);
    }
};
 

